#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/select.h> 
#include <string.h>

#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/stat.h>

int WINDOW_WIDTH = 256;
int WINDOW_HEIGHT = 256;
Bool isMouseDown = False;
void* GuiLiteFb;
pthread_mutex_t mutex;

#define GUILITE_ON  //Do not define this macro once more!!!
#include "GuiLite.h"
#include <stdlib.h>

#define UI_WIDTH 240
#define UI_HEIGHT 320

static c_surface* s_surface;
static c_display* s_display;

class c_star {
public:
	c_star(){
		initialize();
	}
	void initialize() {
		m_x = m_start_x = rand() % UI_WIDTH;
		m_y = m_start_y = rand() % UI_HEIGHT;
		m_size = 1;
		m_x_factor = UI_WIDTH;
		m_y_factor = UI_HEIGHT;
		m_size_factor = 1;
	}
	void move() {
		s_surface->fill_rect(m_x, m_y, m_x + m_size - 1, m_y + m_size - 1, 0, Z_ORDER_LEVEL_0);//clear star footprint

		m_x_factor -= 6;
		m_y_factor -= 6;
		m_size += m_size / 20;
		if (m_x_factor < 1 || m_y_factor < 1)
		{
			return initialize();
		}
		if (m_start_x > (UI_WIDTH / 2) && m_start_y > (UI_HEIGHT / 2))
		{
			m_x = (UI_WIDTH / 2) + (UI_WIDTH * (m_start_x - (UI_WIDTH / 2)) / m_x_factor);
			m_y = (UI_HEIGHT / 2) + (UI_HEIGHT * (m_start_y - (UI_HEIGHT / 2)) / m_y_factor);
		}
		else if (m_start_x <= (UI_WIDTH / 2) && m_start_y > (UI_HEIGHT / 2))
		{
			m_x = (UI_WIDTH / 2) - (UI_WIDTH * ((UI_WIDTH / 2) - m_start_x) / m_x_factor);
			m_y = (UI_HEIGHT / 2) + (UI_HEIGHT * (m_start_y - (UI_HEIGHT / 2)) / m_y_factor);
		}
		else if (m_start_x > (UI_WIDTH / 2) && m_start_y <= (UI_HEIGHT / 2))
		{
			m_x = (UI_WIDTH / 2) + (UI_WIDTH * (m_start_x - (UI_WIDTH / 2)) / m_x_factor);
			m_y = (UI_HEIGHT / 2) - (UI_HEIGHT * ((UI_HEIGHT / 2) - m_start_y) / m_y_factor);
		}
		else if (m_start_x <= (UI_WIDTH / 2) && m_start_y <= (UI_HEIGHT / 2))
		{
			m_x = (UI_WIDTH / 2) - (UI_WIDTH * ((UI_WIDTH / 2) - m_start_x) / m_x_factor);
			m_y = (UI_HEIGHT / 2) - (UI_HEIGHT * ((UI_HEIGHT / 2) - m_start_y) / m_y_factor);
		}

		if (m_x < 0 || (m_x + m_size - 1) >= UI_WIDTH ||
			m_y < 0 || (m_y + m_size - 1) >= UI_HEIGHT)
		{
			return initialize();
		}
		s_surface->fill_rect(m_x, m_y, m_x + m_size - 1, m_y + m_size - 1, GL_RGB(255, 255, 255), Z_ORDER_LEVEL_0);//draw star
	}
	int m_start_x, m_start_y;
	float m_x, m_y, m_x_factor, m_y_factor, m_size_factor, m_size;
};

//////////////////////// start UI ////////////////////////

c_star stars[100];
void create_ui(void* phy_fb, int screen_width, int screen_height, int color_bytes, struct DISPLAY_DRIVER* driver) {
	static c_surface surface(UI_WIDTH, UI_HEIGHT, color_bytes, Z_ORDER_LEVEL_0);
	static c_display display(phy_fb, screen_width, screen_height, &surface, driver);
	s_surface = &surface;
	s_display = &display;

	s_surface->fill_rect(0, 0, UI_WIDTH - 1, UI_HEIGHT - 1, 0, Z_ORDER_LEVEL_0);

	while(1) {
		for (int i = 0; i < sizeof(stars)/sizeof(c_star); i++) {
			stars[i].move();
		}
		thread_sleep(50);
	}
}

//////////////////////// interface for all platform ////////////////////////
extern "C" void startHelloStar(void* phy_fb, int width, int height, int color_bytes, struct DISPLAY_DRIVER* driver) {
	create_ui(phy_fb, width, height, color_bytes, driver);
}

void updateImgFromGuiLite(XImage *img)
{
    if(!GuiLiteFb)
    {
        return;
    }

    unsigned short* pixel = (unsigned short*)GuiLiteFb;
	for (int y = 0; y < WINDOW_HEIGHT; ++y)
    {
       for (int x = 0; x < WINDOW_WIDTH; ++x)
        {
            unsigned int rgb = *pixel++;
            rgb = ((0xFF << 24) | ((((unsigned int)(rgb)) & 0x1F) << 3) | ((((unsigned int)(rgb)) & 0x7E0) << 5) | ((((unsigned int)(rgb)) & 0xF800) << 8));
            XPutPixel(img, x, y, rgb);
        } 
    }
}

void parseArgument(const int& width, const int& height)
{
    int fb_size = (width * height * 2);
    //NOTE 这里申请指定大小的内存块即可
    GuiLiteFb = malloc(fb_size);
    if((void*)-1 != GuiLiteFb)
    {
        memset(GuiLiteFb, 0, fb_size);
    }
    else
    {
        printf("shmat failed, error = %d", errno);
    }
    fflush(stdout);
}

/**
 * @brief 接收键盘和鼠标动作
 * 
 * @param display 
 * @return void* 
 */
void* inputThread(void* display)
{
	Display* dis = static_cast<Display*>(display);
    int x11_fd = ConnectionNumber(dis);
    fd_set in_fds;
    XEvent event;
    struct timeval tv;

    while(1)
    {
        FD_ZERO(&in_fds);
        FD_SET(x11_fd, &in_fds);
        tv.tv_usec = 0;
        tv.tv_sec = 1;

        int num_ready_fds = select(x11_fd + 1, &in_fds, NULL, NULL, &tv);
        if (num_ready_fds > 0)
        {//received event
            pthread_mutex_lock(&mutex);
            while(XPending(dis))
            {
                XNextEvent(dis, &event);//non blocking
                switch(event.type)
                {
                    case ButtonPress:
                        isMouseDown = True;
                        printf("press(%d,%d)\n", event.xbutton.x, event.xbutton.y);
                    break;
                    case ButtonRelease:
                        isMouseDown = False;
                        printf("release(%d,%d)\n", event.xbutton.x, event.xbutton.y);
                    break;
                    case MotionNotify:
                        if(isMouseDown)
                        {
                            printf("press(%d,%d)\n", event.xbutton.x, event.xbutton.y);    
                        }
                    break;
	            case KeyPress:
					printf("%s\n", XKeysymToString(XLookupKeysym(&(event.xkey), 0)));
				    break;
                }
                fflush(stdout);
            }
            pthread_mutex_unlock(&mutex);
        }
    }
}

//TODO 函数更名为Window

void* WindowFunc(void* ptr)
{
    pthread_mutex_init(&mutex, 0);

	//init display
	Display* display = XOpenDisplay(NULL);
	int screen_num = DefaultScreen(display);
	Window root = RootWindow(display, screen_num);
	Visual *visual = DefaultVisual(display, screen_num);
	int colorBits = DefaultDepth(display, screen_num);
	char *data = (char*)malloc(WINDOW_WIDTH * WINDOW_HEIGHT * colorBits);
	
	//Create/Show window
	Window win = XCreateSimpleWindow(display, root, 50, 50, WINDOW_WIDTH, WINDOW_HEIGHT, 1, 0, 0);
	XSelectInput(display, win, ExposureMask | ButtonPressMask| ButtonReleaseMask | PointerMotionMask | KeyPressMask);
	XMapWindow(display, win);

    //Start input thread
    pthread_t pid;
    pthread_create(&pid, NULL, inputThread, display);

    //create img for GuiLite framebuffer
    XImage *img = XCreateImage(display, visual, colorBits, ZPixmap, 0, data, WINDOW_WIDTH, WINDOW_HEIGHT, 32, 0);

	//Main loop
    GC gc = DefaultGC(display,screen_num);
	while(1)
	{
		updateImgFromGuiLite(img);
        pthread_mutex_lock(&mutex);
		XPutImage(display, win, gc, img, 0, 0, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
        pthread_mutex_unlock(&mutex);
		usleep(30000);
	}
	XCloseDisplay(display);
	return 0;
}

int main(int argc, char** argv)
{
	parseArgument(240, 320);
    pthread_t pid;
    pthread_create(&pid, NULL, WindowFunc, NULL);
	int color_bytes = 2;
	int screen_width = 240;
	int screen_height = 320;

	startHelloStar(GuiLiteFb, screen_width, screen_height, color_bytes, NULL);//never return;
    return 0;
}
